package com.mufeng.mflibs.base

import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.fragment.app.Fragment
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.mufeng.mflibs.bean.Resources
import com.mufeng.mflibs.bean.Status
import java.lang.IllegalStateException
import java.lang.reflect.ParameterizedType

abstract class BaseFragment<VM : BaseViewModel>: Fragment() {

    protected lateinit var vm: VM
        private set
    private var shareViewModel = false

    private var pageName: String = javaClass.simpleName

    /** Get the layout resource id from subclass. */
    @LayoutRes
    protected abstract fun getLayoutResId(): Int

    /** Do create view business. */
    protected abstract fun doCreateView(savedInstanceState: Bundle?)

    /**
     * Initialize view model according to the generic class type.
     * Override this method to add your owen implementation.
     *
     * Add [FragmentConfiguration] to the subclass and set
     * [FragmentConfiguration.shareViewModel] true
     * if you want to share view model between several fragments.
     *
     * @return the view model instance.
     */
    protected fun createViewModel(): VM {
        val vmClass = (this.javaClass.genericSuperclass as ParameterizedType).actualTypeArguments
            .firstOrNull { ViewModel::class.java.isAssignableFrom(it as Class<*>) } as? Class<VM>
            ?: throw IllegalStateException("You must specify a view model class.")
        return if (shareViewModel) {
            ViewModelProvider(requireActivity())[vmClass]
        } else {
            ViewModelProvider(this)[vmClass]
        }
    }

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
        val layoutResId = getLayoutResId()
        require(layoutResId > 0) { "The subclass must provider a valid layout resources id." }
        return inflater.inflate(layoutResId, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // fixed 2020-05-21: The callback must be called here
        // to use kotlin android extensions to avoid findViewById()
        doCreateView(savedInstanceState)
    }

    /** Observe data */
    protected fun <T> observe(dataType: Class<T>,
                              success: (res: Resources<T>) -> Unit = {},
                              fail: (res: Resources<T>) -> Unit = {},
                              loading: (res: Resources<T>) -> Unit = {}) {
        observe(dataType, null, false, success, fail, loading)
    }

    /** Observe data */
    protected fun <T> observe(dataType: Class<T>,
                              single: Boolean = false,
                              success: (res: Resources<T>) -> Unit = {},
                              fail: (res: Resources<T>) -> Unit = {},
                              loading: (res: Resources<T>) -> Unit = {}) {
        observe(dataType, null, single, success, fail, loading)
    }

    /** Observe data */
    protected fun <T> observe(dataType: Class<T>,
                              sid: Int? = null,
                              success: (res: Resources<T>) -> Unit = {},
                              fail: (res: Resources<T>) -> Unit = {},
                              loading: (res: Resources<T>) -> Unit = {}) {
        observe(dataType, sid, false, success, fail, loading)
    }

    /** Observe data */
    protected fun <T> observe(dataType: Class<T>,
                              sid: Int? = null,
                              single: Boolean = false,
                              success: (res: Resources<T>) -> Unit = {},
                              fail: (res: Resources<T>) -> Unit = {},
                              loading: (res: Resources<T>) -> Unit = {}) {
        vm.getObservable(dataType, sid, single).observe(this, Observer { res ->
            when (res.status) {
                Status.SUCCESS -> success(res)
                Status.LOADING -> loading(res)
                Status.FAILED -> fail(res)
            }
        })
    }

    /** Observe list data */
    protected fun <T> observeList(dataType: Class<T>,
                                  success: (res: Resources<List<T>>) -> Unit = {},
                                  fail: (res: Resources<List<T>>) -> Unit = {},
                                  loading: (res: Resources<List<T>>) -> Unit = {}) {
        observeList(dataType, null, false, success, fail, loading)
    }

    /** Observe list data */
    protected fun <T> observeList(dataType: Class<T>,
                                  single: Boolean = false,
                                  success: (res: Resources<List<T>>) -> Unit = {},
                                  fail: (res: Resources<List<T>>) -> Unit = {},
                                  loading: (res: Resources<List<T>>) -> Unit = {}) {
        observeList(dataType, null, single, success, fail, loading)
    }

    /** Observe list data */
    protected fun <T> observeList(dataType: Class<T>,
                                  sid: Int? = null,
                                  success: (res: Resources<List<T>>) -> Unit = {},
                                  fail: (res: Resources<List<T>>) -> Unit = {},
                                  loading: (res: Resources<List<T>>) -> Unit = {}) {
        observeList(dataType, sid, false, success, fail, loading)
    }

    /** Observe list data */
    protected fun <T> observeList(dataType: Class<T>,
                                  sid: Int? = null,
                                  single: Boolean = false,
                                  success: (res: Resources<List<T>>) -> Unit = {},
                                  fail: (res: Resources<List<T>>) -> Unit = {},
                                  loading: (res: Resources<List<T>>) -> Unit = {}) {
        vm.getListObservable(dataType, sid, single).observe(this, { res ->
            when (res?.status) {
                Status.SUCCESS -> success(res)
                Status.LOADING -> loading(res)
                Status.FAILED -> fail(res)
            }
        })
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        vm = createViewModel()
        super.onCreate(savedInstanceState)
    }

}